﻿@using Radzen
@using Microsoft.JSInterop
@using Microsoft.AspNetCore.Components.Forms
@typeparam TValue
@inherits FormComponent<TValue>

@if (Visible)
{
    <span @ref="@Element" style="@Style" @attributes="Attributes" class="@GetCssClass()" id="@GetId()">
        <input @ref="@input" type="text" inputmode="decimal" name="@Name" disabled="@Disabled" readonly="@ReadOnly"
               class="rz-spinner-input rz-inputtext" tabindex="@TabIndex"
               placeholder="@Placeholder" autocomplete="@(AutoComplete ? "on" : "off")" value="@FormattedValue" @onchange="@OnChange"
               onkeypress="Radzen.numericKeyPress(event)" onpaste="Radzen.numericOnPaste(event)" />
        @if (ShowUpDown)
        {
            <button type="button" class="rz-spinner-button rz-spinner-up rz-corner-tr rz-button" tabindex="-1"
                    @onclick="@((args) => UpdateValueWithStep(true))">
                <span class="rz-spinner-button-icon rzi rzi-caret-up"></span>
            </button>
            <button type="button" class="rz-spinner-button rz-spinner-down rz-corner-br rz-button" tabindex="-1"
                    @onclick="@((args) => UpdateValueWithStep(false))">
                <span class="rz-spinner-button-icon rzi rzi-caret-down"></span>
            </button>
        }
    </span>
}
@code {
    protected ElementReference input;

    protected override string GetComponentCssClass()
    {
        var fieldCssClass = FieldIdentifier.FieldName != null ? EditContext?.FieldCssClass(FieldIdentifier) : "";

        return $"rz-spinner {(Disabled ? " rz-state-disabled" : "")} {fieldCssClass}";
    }

    async System.Threading.Tasks.Task UpdateValueWithStep(bool stepUp)
    {
        if (Disabled || ReadOnly)
        {
            return;
        }

        var step = string.IsNullOrEmpty(Step) || Step == "any" ? 1 : double.Parse(Step.Replace(",", "."), System.Globalization.CultureInfo.InvariantCulture);

        var valueToUpdate = Value != null ? Convert.ChangeType(Value, typeof(decimal)) : (decimal)Convert.ChangeType(default(decimal), typeof(decimal));

        var newValue = ((decimal)Convert.ChangeType(valueToUpdate, typeof(decimal))) + (decimal)Convert.ChangeType(stepUp ? step : -step, typeof(decimal));

        if (Max.HasValue && newValue > Max.Value || Min.HasValue && newValue < Min.Value || object.Equals(Value, newValue))
        {
            return;
        }

        Value = (TValue)ConvertType.ChangeType(newValue, typeof(TValue));

        await ValueChanged.InvokeAsync(Value);
        if (FieldIdentifier.FieldName != null) { EditContext?.NotifyFieldChanged(FieldIdentifier); }
        await Change.InvokeAsync(Value);

        StateHasChanged();
    }

    [Parameter]
    public override TValue Value
    {
        get
        {
            return _value;
        }
        set
        {
            if (!EqualityComparer<TValue>.Default.Equals(value, _value))
            {
                _value = value;
            }
        }
    }

    protected string FormattedValue
    {
        get
        {
            if (Value != null)
            {
                if (Format != null)
                {
                    decimal decimalValue = (decimal)Convert.ChangeType(Value, typeof(decimal));
                    return decimalValue.ToString(Format);
                }
                return Value.ToString();
            }
            else
            {
                return "";
            }
        }
        set
        {
            _ = InternalValueChanged(value);
        }
    }

    public override bool HasValue
    {
        get
        {
            return Value != null;
        }
    }

    [Parameter]
    public string Format { get; set; }

    [Parameter]
    public string Step { get; set; }

    private bool IsTypeSupported()
    {
        var type = typeof(TValue).IsGenericType ? typeof(TValue).GetGenericArguments()[0] : typeof(TValue);

        switch (Type.GetTypeCode(type))
        {
            //case TypeCode.Byte:
            //case TypeCode.SByte:
            //case TypeCode.UInt16:
            case TypeCode.UInt32:
            //case TypeCode.UInt64:
            //case TypeCode.Int16:
            case TypeCode.Int32:
            //case TypeCode.Int64:
            case TypeCode.Decimal:
            case TypeCode.Double:
            case TypeCode.Single:
                return true;
            default:
                return false;
        }
    }

    [Parameter]
    public bool ReadOnly { get; set; }

    [Parameter]
    public bool AutoComplete { get; set; } = true;

    [Parameter]
    public bool ShowUpDown { get; set; } = true;

    protected async System.Threading.Tasks.Task OnChange(ChangeEventArgs args)
    {
        await InternalValueChanged(args.Value);
    }

    private string RemoveNonNumericCharacters(object value)
    {
        string valueStr = value as string;
        if (valueStr == null)
        {
            valueStr = value.ToString();
        }
        return new string(valueStr.Where(c => char.IsDigit(c) || char.IsPunctuation(c)).ToArray());
    }

    private async System.Threading.Tasks.Task InternalValueChanged(object value)
    {
        TValue newValue;
        BindConverter.TryConvertTo<TValue>(RemoveNonNumericCharacters(value), System.Globalization.CultureInfo.CurrentCulture, out newValue);

        decimal? newValueAsDecimal = newValue == null ? default(decimal?) : (decimal)ConvertType.ChangeType(newValue, typeof(decimal));

        if (object.Equals(Value, newValue) && !ValueChanged.HasDelegate)
        {
            await JSRuntime.InvokeAsync<string>("Radzen.setInputValue", input, Value);
            return;
        }

        if (Max.HasValue && newValueAsDecimal > Max.Value)
        {
            newValueAsDecimal = Max.Value;
        }

        if (Min.HasValue && newValueAsDecimal < Min.Value)
        {
            newValueAsDecimal = Min.Value;
        }

        Value = (TValue)ConvertType.ChangeType(newValueAsDecimal, typeof(TValue));
        if (!ValueChanged.HasDelegate)
        {
            await JSRuntime.InvokeAsync<string>("Radzen.setInputValue", input, Value);
        }

        await ValueChanged.InvokeAsync(Value);
        if (FieldIdentifier.FieldName != null) { EditContext?.NotifyFieldChanged(FieldIdentifier); }
        await Change.InvokeAsync(Value);
    }

    [Parameter]
    public decimal? Min { get; set; }

    [Parameter]
    public decimal? Max { get; set; }

    public override async Task SetParametersAsync(ParameterView parameters)
    {
        bool minChanged = parameters.DidParameterChange(nameof(Min), Min);
        bool maxChanged = parameters.DidParameterChange(nameof(Max), Max);

        await base.SetParametersAsync(parameters);

        if (minChanged && Min.HasValue && Value != null && IsJSRuntimeAvailable)
        {
            decimal decimalValue = (decimal)Convert.ChangeType(Value, typeof(decimal));
            if (decimalValue < Min.Value)
            {
                await InternalValueChanged(Min.Value);
            }
        }

        if (maxChanged && Max.HasValue && Value != null && IsJSRuntimeAvailable)
        {
            decimal decimalValue = (decimal)Convert.ChangeType(Value, typeof(decimal));
            if (decimalValue > Max.Value)
            {
                await InternalValueChanged(Max.Value);
            }
        }
    }
}